// --------------------------------------------------------------------------------------------------------------------
// <copyright file="StepBaseTest.cs" company="">
//   
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Tests
{
    using System;
    using System.Threading;

    using ExecutionTool.Common;

    using Microsoft.VisualStudio.TestTools.UnitTesting;

    [TestClass]
    public class StepBaseTest
    {
        #region Public Methods and Operators

        [TestMethod]
        public void CanStillGetTheLastReportedProgressAfterAborted()
        {
            // let the internal timer elapse in half a second
            var step = new MockStep(500) { ProgressHeartbeatActionFactory = () => 50 };

            step.Start();

            // wait till the internal timer updates the progress.
            Thread.Sleep(1000);
            step.Abort();

            Assert.AreEqual(StepState.Aborted, step.StepState);
            var progress = step.GetProgress();
            Assert.AreEqual(50, progress);
        }

        [ExpectedException(typeof(InvalidOperationException))]
        [TestMethod]
        public void StepCanBeAbortedOnlyWhenRunning()
        {
            var step = new MockStep();

            step.Abort();

            Assert.AreEqual(StepState.Aborted, step.StepState);
        }

        [TestMethod]
        public void StepCanBeAbortedWhenRunning()
        {
            var step = new MockStep();

            step.Start();
            step.Abort();

            Assert.AreEqual(StepState.Aborted, step.StepState);
        }

        [TestMethod]
        public void StepCanBeCreatedAndInitialized()
        {
            var step = new MockStep();

            Assert.AreEqual(StepState.Initialized, step.StepState);
        }

        [TestMethod]
        public void StepCanBeStartedAndTransitionIntoRunningState()
        {
            var step = new MockStep();

            step.Start();

            Assert.AreEqual(StepState.Running, step.StepState);
        }

        [ExpectedException(typeof(InvalidOperationException))]
        [TestMethod]
        public void StepCanBeStartedOnlyOnce()
        {
            var step = new MockStep();

            step.Start();
            step.Start();
        }

        [TestMethod]
        public void StepCanNotifyIfTransitioningToFaultedStateInAbortAction()
        {
            const string ExceptionMessage = "Fake exception.";

            // 0.5 second tick
            var step = new MockStep(500) { AbortActionFactory = () => { throw new Exception(ExceptionMessage); } };
            var e = new ManualResetEvent(false);
            FaultedEventArgs argsToVerify = null;
            step.Faulted += (sender, args) =>
                {
                    e.Set();
                    argsToVerify = args;
                };
            step.Start();
            step.Abort();

            Assert.IsTrue(e.WaitOne(1000));
            Assert.AreEqual(StepState.Faulted, step.StepState);
            Assert.AreEqual(FaultedLocation.Abort, argsToVerify.FaultedLocation);
            Assert.AreEqual(ExceptionMessage, argsToVerify.Exception.Message);
        }

        [TestMethod]
        public void StepCanNotifyIfTransitioningToFaultedStateInProgressHeartbeatAction()
        {
            const string ExceptionMessage = "Fake exception.";

            // 0.5 second tick
            var step = new MockStep(500) { ProgressHeartbeatActionFactory = () => { throw new Exception(ExceptionMessage); } };
            var e = new ManualResetEvent(false);
            FaultedEventArgs argsToVerify = null;
            step.Faulted += (sender, args) =>
                {
                    e.Set();
                    argsToVerify = args;
                };
            step.Start();

            // wait for at most 5 seconds for the exception retry mechanism
            Assert.IsTrue(e.WaitOne(5000));
            Assert.AreEqual(StepState.Faulted, step.StepState);
            Assert.AreEqual(FaultedLocation.ProgressHeartbeat, argsToVerify.FaultedLocation);
            Assert.AreEqual(ExceptionMessage, argsToVerify.Exception.Message);
        }

        [TestMethod]
        public void StepCanNotifyIfTransitioningToFaultedStateInStartAction()
        {
            const string ExceptionMessage = "Fake exception.";
            var step = new MockStep { StartActionFactory = () => { throw new Exception(ExceptionMessage); } };
            var e = new ManualResetEvent(false);
            FaultedEventArgs argsToVerify = null;
            step.Faulted += (sender, args) =>
                {
                    e.Set();
                    argsToVerify = args;
                };
            step.Start();

            Assert.IsTrue(e.WaitOne(1000));
            Assert.AreEqual(StepState.Faulted, step.StepState);
            Assert.AreEqual(FaultedLocation.Start, argsToVerify.FaultedLocation);
            Assert.AreEqual(ExceptionMessage, argsToVerify.Exception.Message);
        }

        [TestMethod]
        public void StepCanNotifyIfTransitioningToFinishedState()
        {
            // make sure the step finish before the wait handle timeout
            var step = new MockStep(500) { ProgressHeartbeatActionFactory = () => 100 };
            var e = new ManualResetEvent(false);
            step.Finished += (sender, args) => e.Set();
            step.Start();

            Assert.IsTrue(e.WaitOne(1000));
            Assert.AreEqual(100, step.GetProgress());
            Assert.AreEqual(StepState.Finished, step.StepState);
        }

        [TestMethod]
        public void StepCanNotifyProgress()
        {
            // make sure the step finish before the wait handle timeout
            var step = new MockStep(500) { ProgressHeartbeatActionFactory = () => 50 };
            ProgressedEventArgs p = null;
            var e = new ManualResetEvent(false);
            step.Progressed += (sender, args) =>
                {
                    e.Set();
                    p = args;
                };
            step.Start();

            Assert.IsTrue(e.WaitOne(1000));
            Assert.AreEqual(50, step.GetProgress());
            Assert.AreEqual(50, p.Progress);
            Assert.AreEqual(StepState.Running, step.StepState);
        }

        #endregion
    }
}
